using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace XTYAPI.通讯
{
    public class ModbusTCP
    {
        Socket ModbusTcpClient;
        ushort ID = 0;        
        private byte[] MBAP = new byte[] { 00, 00, 00, 00, 00 };
        bool connect;
        int tcpErr;
        string ip;
        int port;
        int modbusErr;
        /// <summary>
        /// 超时时基10ms
        /// </summary>
        public int TimeOut { get; set; } = 10;
        object lockModbus = new object();
        public bool RestConnect()
        {
            return Connect(ip, port);
        }
        public bool Connect(string ip, int port)
        {
            this.ip = ip; this.port = port;
            if (ModbusTcpClient != null)
            {
                ModbusTcpClient.Close();
            }
            ModbusTcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//创建sokcet tcp连接对象
            IAsyncResult asyncResult = ModbusTcpClient.BeginConnect(new IPEndPoint(IPAddress.Parse(ip), port), null, null);//异步连接
            asyncResult.AsyncWaitHandle.WaitOne(3000, true);//阻塞最多等待3秒
            if (!asyncResult.IsCompleted)//异步操作没完成 就关掉tcp连接 返回失败
            {
                ModbusTcpClient.Close();
                connect = false;
                return false;
            }
            connect = true;
            modbusErr = 0;
            tcpErr = 0;
            return true;
        }
        public void DisConnect()
        {
            if (ModbusTcpClient != null)
            {
                ModbusTcpClient.Close();
            }
        }
        public bool IsConnected
        {
            get
            {
                try
                {
                    //bool con = !(ModbusTcpClient.Poll(10, SelectMode.SelectRead) && ModbusTcpClient.Available == 0) && connect;                        
                    return connect && modbusErr <= 10&&tcpErr<=5;
                }
                catch (Exception)
                {
                    return false;
                }
            }
        }
        // MODBUS读保持寄存器 
        //主站请求：00 00 00 00 00 06  00 03 00 00 00 01
        //地址    1字节（无意义,因为是TCP协议，在串口服务器里有意义）
        //功能码  1字节   0x03 批量读数据
        //起始寄存器地址  2字节   
        //寄存器数量  2字节   
        public byte[] ReadKeepReg(uint iAddress, int iLength)
        {
            byte[] SendCommand = new byte[12];
            //ModbusTCP报文封装
            ID++;
            byte[] id = BitConverter.GetBytes(ID);
            SendCommand[0] = id[1]; //此次通信事务处理标识符2个字节
            SendCommand[1] = id[0];

            SendCommand[2] = MBAP[2];//==00该指令遵循modbusTcp; 2个字节
            SendCommand[3] = MBAP[3];

            SendCommand[4] = 00;//后面数据字节长度
            SendCommand[5] = 06;

            SendCommand[6] = 00;//站地址无
            SendCommand[7] = 03;//功能码

            SendCommand[8] = (byte)((iAddress - iAddress % 256) / 256);//寄存器地址 2个字节
            SendCommand[9] = (byte)(iAddress % 256);


            SendCommand[10] = (byte)((iLength - iLength % 256) / 256);//寄存器个数 2个字节
            SendCommand[11] = (byte)(iLength % 256);
            byte[] ResByte = SendRecive(SendCommand);
            if (ResByte?.Length == 9 + iLength * 2 && ResByte[0] == SendCommand[0] && ResByte[1] == SendCommand[1] && ResByte[7] == 03 && ResByte[8] == iLength * 2)
            {
                byte[] userByte = new byte[iLength * 2];
                for (int i = 0; i < userByte.Length; i++)
                {
                    userByte[i] = ResByte[9 + i];
                }
                modbusErr = 0;
                return userByte;
            }
            modbusErr++;
            return null;
        }
        /// <summary>
        /// 写保持寄存器
        /// </summary>
        /// <param name="iAddress">字地址</param>
        /// <param name="arry">要写入的字节数组2的倍数</param>
        /// <returns></returns>
        public bool WriteKeepReg(int iAddress, byte[] arry)
        {
            byte byteCount = (byte)arry.Length;
            byte wordCount = (byte)(arry.Length / 2);
            int Conut = 7 + byteCount;
            byte[] SendCommand = new byte[13 + byteCount];
            //ModbusTCP报文封装
            ID++;
            byte[] id = BitConverter.GetBytes(ID);
            SendCommand[0] = id[1]; //此次通信事务处理标识符2个字节
            SendCommand[1] = id[0];

            SendCommand[2] = MBAP[2];//==00该指令遵循modbusTcp; 2个字节
            SendCommand[3] = MBAP[3];

            SendCommand[4] = 0;//后面数据字节长度
            SendCommand[5] = (byte)Conut;

            SendCommand[6] = 0;//站地址无
            SendCommand[7] = 0x10;//功能码

            SendCommand[8] = (byte)((iAddress - iAddress % 256) / 256);//起始寄存器地址 2个字节
            SendCommand[9] = (byte)(iAddress % 256);

            SendCommand[10] = 0;//写寄存器数量 2个字节
            SendCommand[11] = wordCount;

            SendCommand[12] = byteCount;//写寄存器个数 2个字节（*2）
            for (int i = 0; i < byteCount; i++)//数据内容
            {
                SendCommand[i + 13] = arry[i];
            }
            byte[] ResByte = SendRecive(SendCommand);
            if (ResByte?.Length > 11 && ResByte[0] == SendCommand[0] && ResByte[1] == SendCommand[1] && ResByte[7] == 16 && ResByte[11] == arry.Length / 2)//报文正确判定
            {
                modbusErr = 0;
                return true;
            }
            modbusErr++;
            return false;
        }
        public byte[] SendRecive(byte[] arry)
        {
            int delay = 0;
            try
            {
                Monitor.Enter(lockModbus);
                int reslut = ModbusTcpClient.Send(arry);
                while (ModbusTcpClient.Available == 0)
                {
                    Thread.Sleep(10);
                    delay++;
                    if (delay > TimeOut)
                    {
                        break;
                    }
                }
                byte[] ResByte = new byte[ModbusTcpClient.Available];
                reslut = ModbusTcpClient.Receive(ResByte);
                tcpErr = 0;
                return ResByte;
            }
            catch (Exception)
            {
                tcpErr++;
                return null;
            }
            finally
            {
                Monitor.Exit(lockModbus);
            }
        }

    }
}
